
function wait_time(centri_count, q)
    -- one centrifuge works on one pair for 5 seconds
    local time = 5;
    -- how much time to wait for each batch
    -- math.ceil() steers towards the safe side to avoid bursting tubes
    if q ~= nil or q ~= 0 then
        time = math.ceil ((q  * time) / centri_count);
    end
    return time;
end

-- inventory-aware version

local function showlist(channel, list, empty_msg)
    empty_msg = empty_msg or "Empty List."
    local size = table.maxn(list)
    if size <= 0 then
        digiline_send(channel, empty_msg)
        return
    end
    local longstr= ""
    for _, v in ipairs(list) do
        longstr = longstr .. v.name .. ", ".. v.count .."; \n"
    end
    digiline_send(channel, longstr)
end

local function is_in_list(string, list)
    for i, v in ipairs(list) do
        if v.name == string then
            return i;
        end
    end
    return -1
end

local function additem(list, stack)
    local index = is_in_list(stack.name, list)
-- if the item is not in the list or the list is empty
    if (index < 0 or table.maxn(list) <= 0) then
        if stack.count > 0 then
            table.insert(list, stack)
        end
        -- if count < 0 then the microcontroller was initialized
        -- as the digilines chest had items in it
        return
    else
-- if the item is in the list and it must be updated
        list[index].count = list[index].count + stack.count
    end
    if list[index].count <= 0 then
        table.remove(list, index)
    end
end

local function detect(msg, list)
    if msg  == nil then 
        return 
    end
    if msg.stack == nil then 
        return 
    end
    local n = msg.stack.name
    local c = msg.stack.count
    if msg.action == "uput" then
    -- player has put an item stack in the digichest
        additem(list, {name=n, count= c})
    elseif msg.action == "utake" then
    -- player has taken an item
        additem(list, {name=n, count= -c})
    elseif msg.action  == "tput" then
    -- a tube has put an item
        additem(list, {name=n, count= c})
--        showlist("lcd", mem.paid)
    elseif msg.action == "ttake" then
    -- a tube has taken an item
        additem(list, {name=n, count= -c})
    end
end

local function exclude(itemname)
    local LCD = "lcd3"
    local excluded = false
    local a,b = string.find(itemname, "technic:uranium", 1, true)
    if not string.find(itemname, "technic:uranium", 1, true) then
        -- item is not uranium - skip
        digiline_send(LCD, "warning: item is not uranium!")
        excluded = true
    elseif string.find(itemname, "technic:uranium0", 1, true) then
        digiline_send(LCD, "uranium0 - skipping")
        excluded = true
    end
    return excluded
end

local function pick()
    local LCD = "lcd3"
    if mem.count == 0 then
        mem.count = 1
    end
    if table.maxn(mem.stash) <= 0 then 
        return 0
    end
    local item = mem.stash[1]
    if item == nil then
        digiline_send(LCD, "item is nil - i="..i)
        return 0
    end
    if exclude(item.name) then
        -- pop from list
        digiline_send("dfi", item) 
        return 0
    end
    local stacksize
    local amount = 2 -- to send
    -- the maximum stack size in the game by default is 99
    local MAX_QUANTITY = 99
    local remainder = item.count % MAX_QUANTITY
    if item.count > MAX_QUANTITY and remainder > 0 then
        -- if there are multiple stacks, then consider the smaller one
        stacksize = item.count % MAX_QUANTITY
    elseif item.count > MAX_QUANTITY and remainder == 0 then
        -- if there are multiple 99 stacks, then consider one of them
        stacksize = MAX_QUANTITY
    else
        stacksize = item.count
    end
    if stacksize == 1 then
        -- send quantities of one to pop it from list
        -- the tube will return it to input
        amount = stacksize
    elseif stacksize % 2 == 1 then
        -- there is an odd number, so send one less
        digiline_send("dfi", {name=item.name, count=1})
        amount = stacksize - 1
    else
        amount = stacksize
    end
    digiline_send( "dfi", {name=item.name, count=amount})
    digiline_send( LCD, "Sent "..  amount .. " " .. item.name)
    return amount
end

local function scheduler(max_cycle, bias)
    local TIMER_LIMIT = 60
    local LCD         = "lcd"
    local CENTRI_COUNT = mem.machines
    mem.timer     = (mem.timer + 1) % TIMER_LIMIT
    local display = "Next dispatch @ " .. mem.time_next .. "s (".. mem.timer..")" 
    digiline_send(LCD, display)
    if mem.timer ~= mem.time_next then
        -- not equal is used as opposed to >= because
        -- of '% TIMER_LIMIT'
        return
    end
    local n = pick()
    local interval = 1
    if n > 1 then -- 0 = no stack sent, 1 = rejected by filter
        interval = wait_time(CENTRI_COUNT, n)
    end
    mem.time_next = (mem.timer + interval) % TIMER_LIMIT
end

-- user interface

function touch_init(channel, state)
    local reset = {}
    reset.command = "clear"
    digiline_send(channel, reset)

    local button = {}
    button.command = "addbutton"
    button["X"] = 0
    button["Y"] = 0
    button["W"] = 10
    button["H"] = 1
    button["name"] = "onoff"

    if state then
        button["label"] = "Stop"
    else
        button["label"] = "Start"
    end
    digiline_send(channel, button)

    local field1 = {}
    field1.command = "addfield"
    field1["X"] = 1
    field1["Y"] = 2
    field1["W"] = 8
    field1["H"] = 1
    field1["name"] = "machines"
    field1["label"] = "Change number of active centrifuges."
    field1["default"] = "Enter value greater than 0. Current: " .. mem.machines
    digiline_send(channel, field1)
end

function touch_response(message)
    if message["key_enter_field"] == nil and message["quit"] ~= nil then
        -- ignore the event when a player closes menu
        return
    end
    if message["onoff"] ~= nil then
        mem.state = not mem.state
        if mem.state then
            digiline_send("lcd", "Restarting dispatch.")
        else 
            digiline_send("lcd", "Stopped dispatch.")
        end
    elseif message["key_enter_field"] == "machines" then
        -- check if the value is numeric
        local val = tonumber(message["machines"])
        if val ~= nil and val > 0 then
            mem.machines = val
            digiline_send("lcd", "Active centrifuges = " .. mem.machines)
        else
            digiline_send("lcd", "Invalid input.")
        end
    end -- end ifs
end -- end function

function main()
    local CONST_T = 1
    if event.type == "program" then
        -- variables that must be preserved between interrupts
        mem.timer     = 0
        mem.time_next = 1
        -- variables that are altered by touchscreen
        mem.state     =  true
        mem.machines  = 1
        touch_init("touchscreen", mem.state)
        mem.stash = {}
        digiline_send("lcd2", "Initialized!")
    elseif event.type == "interrupt" and mem.state then
        -- send_uranium(mem.max_cycle, mem.init_bias)
        scheduler(mem.max_cycle, mem.init_bias)
    elseif event.type == "digiline" and event.channel == "input" then
        detect(event.msg, mem.stash)
        showlist("lcd2", mem.stash, "Input is empty.")
    elseif event.type == "digiline" and event.channel == "touchscreen" then
        touch_response(event.msg)
        touch_init(event.channel, mem.state)
    end
    if event.type == "interrupt" 
    or event.type == "program" then
        if mem.state then port.d = not port.d end
        interrupt(CONST_T)
    end
end

return main()
